using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleSocket
{
    internal static class SocketConnector
    {
        private static TaskCompletionSource<Socket> _task = null;


        internal static Task<Socket> CreateSocket(IPEndPoint remoteEndpoint)
        {
            var source = new TaskCompletionSource<Socket>();
            var socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            var e = new SocketAsyncEventArgs();
            e.UserToken = new Tuple<TaskCompletionSource<Socket>, Socket>(source, socket);
            e.RemoteEndPoint = remoteEndpoint;
            e.Completed += OnCompleted;
            
            var completed = true;
            try
            {
                completed = socket.ConnectAsync(e);
            }
            catch (Exception ex)
            {
                source.TrySetException(ex);
            }

            if (!completed) ThreadPool.QueueUserWorkItem(_ => OnCompleted(null, e));

            _task = source;
            Task.Delay(5000).ContinueWith(_ =>
            {
                _task?.TrySetException(new SocketException((int)SocketError.TimedOut));
            });

            return source.Task;
        }

        private static void OnCompleted(object sender, SocketAsyncEventArgs e)
        {
            _task = null;
            var t = e.UserToken as Tuple<TaskCompletionSource<Socket>, Socket>;
            var source = t.Item1;
            var socket = t.Item2;
         
            var error = e.SocketError;

            e.UserToken = null;
            e.Completed -= OnCompleted;
            e.Dispose();

            if (error != SocketError.Success)
            {
                socket.Close();
                source.TrySetException(new SocketException((int)error));
                return;
            }

            source.TrySetResult(socket);
        }
    }
}